#include <stdio.h>
#include <png.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>
#include <stdlib.h>


#define FB_DEVICE_NAME "/dev/fb0"
int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;

typedef struct PictureData {
	FILE *ptFp;//文件句柄
	int iBpp;
	int iRgbSize;
	
	int iWidth;
	int iHeight;
	unsigned char *pucRgbData;

	/* use for PNG */
	png_structp ptPngStrPoint;//png结构体指针
	png_infop ptPngInfoPoint;//png信息结构体指针
	unsigned char *pucRawData;//原始rgba数据
	int iRawSize;//原始rgba数据大小
	int iChannels;//原始数据通道数
}T_PictureData, *PT_PictureData;

/**********************************************************************
 * 函数名称： IsnotPng
 * 功能描述：判断是否为PNG文件
 * 输入参数： ppFp - 文件句柄指针
 					strFileName - 文件名
 * 返 回 值：0 - 是PNG格式 其他-不是PNG格式
 ***********************************************************************/
int IsnotPng(FILE **ppFp, const char *strFileName) 
{
	char strCheckHeader[8]; 
	*ppFp= fopen(strFileName, "rb");
	if (*ppFp== NULL) {
		return -1;
	}
	/* 读取PNG文件前8个字节，使用库函数png_sig_cmp即可判断是否为PNG格式 */
	if (fread(strCheckHeader, 1, 8, *ppFp) != 8) 
		return -1;
	return png_sig_cmp(strCheckHeader, 0, 8); 

}

/**********************************************************************
 * 函数名称： RgbaToRgb
 * 功能描述：判断是否为PNG文件
 * 输入参数： ptData - 内含文件信息
 * 输出参数：PT_PictureData->pucRgbData - 内含rgb数据
 * 返 回 值：0 - 成功 其他-失败
 ***********************************************************************/
int RgbaToRgb(PT_PictureData ptData){
	int i ,j;
	int iPos = 0;
	ptData->iRgbSize= ptData->iHeight*ptData->iWidth * 3; 
	ptData->pucRgbData = (unsigned char*)malloc(ptData->iRgbSize);
	if (NULL == ptData->pucRgbData) {
	printf("malloc rgb faile ...\n");
	png_destroy_read_struct(&ptData->ptPngStrPoint, &ptData->ptPngInfoPoint, 0);
	fclose(ptData->ptFp);
	return -1;
	}

	for(i = 0;i<ptData->iHeight;i++)
	{
		for(j = 0;j<ptData->iWidth*4;j+=4){
		ptData->pucRgbData[iPos++] = ptData->pucRawData[i*ptData->iWidth*4+j+3];
		ptData->pucRgbData[iPos++] = ptData->pucRawData[i*ptData->iWidth*4+j+2] ;
		ptData->pucRgbData[iPos++] = ptData->pucRawData[i*ptData->iWidth*4+j+1];
		}
	}
	ptData->iBpp = 24;
	free(ptData->pucRawData);
	return 0;
}

/**********************************************************************
 * 函数名称： DecodePng2Rgb
 * 功能描述：把PNG文件解析为RGB888格式
 * 输入参数： ptData - 内含文件信息
 *                             strFileName - 文件名
 * 输出参数：PT_PictureData->pucRgbData - 内含rgb数据
 * 返 回 值：0 - 成功 其他-失败
 ***********************************************************************/
static int DecodePng2Rgb(const char *strFileName, PT_PictureData ptData) 
{	 
	int i, j;
	int iPos = 0;
	png_bytepp pucPngData; 
	/* 0.判断该文件是否为PNG格式 */
	if (IsnotPng(&ptData->ptFp, strFileName)) {
		printf("file is not png ...\n");
		return -1;
	} 

	/* 1.分配和初始化两个与libpng相关的结构体png_ptr，info_ptr */
	ptData->ptPngStrPoint  = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 
	ptData->ptPngInfoPoint= png_create_info_struct(ptData->ptPngStrPoint);

 	/* 2.设置错误的返回点 */
	setjmp(png_jmpbuf(ptData->ptPngStrPoint));
	rewind(ptData->ptFp); //等价fseek(fp, 0, SEEK_SET);

 	/* 3.指定源文件 */
	png_init_io(ptData->ptPngStrPoint, ptData->ptFp);

	/* 4.获取PNG图像数据信息和通道数，宽度，高度等 
	  * 使用PNG_TRANSFORM_EXPAND宏做参数的作用是根据通道数的不同，
	  * 将PNG图像转换为BGR888或ABGR8888格式*/
	png_read_png(ptData->ptPngStrPoint, ptData->ptPngInfoPoint, PNG_TRANSFORM_EXPAND, 0); 
	ptData->iChannels 	= png_get_channels(ptData->ptPngStrPoint, ptData->ptPngInfoPoint); 
	ptData->iWidth 	 = png_get_image_width(ptData->ptPngStrPoint, ptData->ptPngInfoPoint);
	ptData->iHeight  = png_get_image_height(ptData->ptPngStrPoint, ptData->ptPngInfoPoint);


	/* 5.将info_ptr中的图像数据读取出来 */
	pucPngData = png_get_rows(ptData->ptPngStrPoint, ptData->ptPngInfoPoint); //也可以分别每一行获取png_get_rowbytes();
	if (ptData->iChannels == 4) { //判断是24位还是32位
		ptData->iRawSize= ptData->iWidth * ptData->iHeight*4; //申请内存先计算空间	
		ptData->pucRawData= (unsigned char*)malloc(ptData->iRawSize);
		if (NULL == ptData->pucRawData) {
			printf("malloc rgba faile ...\n");
			png_destroy_read_struct(&ptData->ptPngStrPoint, &ptData->ptPngInfoPoint, 0);
			fclose(ptData->ptFp);
			return -1;
		}
		/* 从pucPngData里读出实际的RGBA数据出来 
		 * 源数据为ABGR格式*/
		for (i = 0; i < ptData->iHeight; i++) 
			for (j = 0; j < ptData->iWidth * 4; j += 4) {
					ptData->pucRawData[iPos++] = pucPngData[i][j + 3];
					ptData->pucRawData[iPos++] = pucPngData[i][j + 2];
					ptData->pucRawData[iPos++] = pucPngData[i][j + 1];
					ptData->pucRawData[iPos++] = pucPngData[i][j + 0];
				}

		/* 将得到的RGBA转换为RGB888格式 */
		if(RgbaToRgb(ptData)!=0)
			return -1;

	}
	else if (ptData->iChannels == 3 ) { //判断颜色深度是24位还是32位
		ptData->iRgbSize= ptData->iWidth * ptData->iHeight*3; //申请内存先计算空间	
		ptData->pucRgbData = (unsigned char*)malloc(ptData->iRgbSize);
		if (NULL == ptData->pucRgbData) {
			printf("malloc rgba faile ...\n");
			png_destroy_read_struct(&ptData->ptPngStrPoint, &ptData->ptPngInfoPoint, 0);
			fclose(ptData->ptFp);
			return -1;
		}
		/* 从pucPngData里读出实际的RGB数据
		  * 源数据为BGR格式*/
		for (i = 0; i < ptData->iHeight; i ++) {
			for (j = 0; j < ptData->iWidth*3; j += 3) {
				ptData->pucRgbData[iPos++] = pucPngData[i][j+2];
				ptData->pucRgbData[iPos++] = pucPngData[i][j+1];
				ptData->pucRgbData[iPos++] = pucPngData[i][j+0];
			}
		}
		ptData->iBpp = 24;//转化之后的格式为RGB888格式
	} 
	else return -1; 

	
	/* 6:销毁内存 */
	png_destroy_read_struct(&ptData->ptPngStrPoint, &ptData->ptPngInfoPoint, 0);
	fclose(ptData->ptFp);


	return 0;
} 



/**********************************************************************
 * 函数名称： ReleasePng2Rgb
 * 功能描述：释放解析及使用PNG时使用的内存
 * 输入参数： ptPicData - 内含文件信息
 * 返 回 值：无
 ***********************************************************************/
static void ReleasePng2Rgb(PT_PictureData ptPicData){
	free(ptPicData->pucRgbData);
}

/**********************************************************************
 * 函数名称： FBDeviceInit
 * 功能描述： "framebuffer显示设备"的初始化函数
 * 输入参数： 无
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int FBDeviceInit(void)
{
	int ret;
	
	fd_fb = open(FB_DEVICE_NAME, O_RDWR);
	if (0 > fd_fb)
	{
		printf("can't open %s\n", FB_DEVICE_NAME);
	}

	ret = ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
	if (ret < 0)
	{
		printf("can't get fb's var\n");
		return -1;
	}

	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;  //整屏全部像素点所占的字节数：800*480*32/8=1536000Byte = 1500KB ≈ 1.46MB
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

    line_width  = var.xres * var.bits_per_pixel / 8;  //每一行像素点所占据的字节数 800*32/8bit = 
	pixel_width = var.bits_per_pixel / 8;  //一个像素点的宽度 32/8bit = 4Byte
	
	return 0;
}

/**********************************************************************
 * 函数名称： lcd_put_pixel
 * 功能描述： 在LCD指定位置上输出指定颜色（描点）
 * 输入参数： x坐标，y坐标，颜色
 * 输出参数： 无
 * 返 回 值： 会
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned int *pen_32 = (unsigned int *)(fbmem+y*line_width+x*pixel_width);
	*pen_32 = color;
}

/**********************************************************************
 * 函数名称： FbShowPicture
 * 功能描述：将转换后的rgb数据显示到LCD上
 * 输入参数： ptData - 内含图像信息
 * 返 回 值： 无
 ***********************************************************************/
static void FbShowPicture(PT_PictureData ptData){
	int i ,j;
	int iColor;
	for(i = 0; i<ptData->iHeight; i++){
		for(j = 0; j<ptData->iWidth*3; j+=3){
			if(j/3<var.xres && i<var.yres){
				iColor = (ptData->pucRgbData[i*ptData->iWidth*3+j+0]<<16) + (ptData->pucRgbData[i*ptData->iWidth*3+j+1]<<8) + (ptData->pucRgbData[i*ptData->iWidth*3+j+2]<<0);
				lcd_put_pixel(j/3, i, iColor);
			}
		}
	}
}

int main(int argc,char *argv[])
{
    T_PictureData ptData;
    int ret;

    ret = FBDeviceInit();
    printf("ret = %d\n", ret);

    ret = DecodePng2Rgb(argv[1], &ptData);
    printf("ret = %d\n", ret);

    FbShowPicture(&ptData);

    ReleasePng2Rgb(&ptData);

    return 0;
}